package cn.dansj.common.utils.io;

import cn.dansj.common.utils.jdk.Preconditions;
import cn.dansj.common.utils.lang.Nullable;
import cn.dansj.common.utils.reflect.ClassUtils;
import cn.dansj.common.utils.transfer.AssertUtils;
import cn.dansj.common.utils.transfer.Transformation;

import java.io.*;

public abstract class IOUtils {
    private static final InputStream nullInputStream = ClassUtils.hasMethod(InputStream.class, "nullInputStream") ? invokeNullInputStream() : defaultNullInputStream();
    public static final int BUFFER_SIZE = 4096;

    public static InputStream nullInputStream() {
        return Transformation.lambdaTryCatch(() -> new ByteArrayInputStream(copyToByteArray(nullInputStream)), IOUtils::defaultNullInputStream);
    }

    private static InputStream invokeNullInputStream() {
        return Transformation.lambdaTryCatch(() -> (InputStream) ClassUtils.getMethod(InputStream.class, "nullInputStream").invoke(InputStream.class), IOUtils::defaultNullInputStream);
    }

    public static byte[] copyToByteArray(@Nullable InputStream in) throws IOException {
        if (in == null) {
            return new byte[0];
        }

        ByteArrayOutputStream out = new ByteArrayOutputStream(BUFFER_SIZE);
        copy(in, out);
        return out.toByteArray();
    }

    public static int copy(InputStream in, OutputStream out) throws IOException {
        AssertUtils.notNull(in, "No InputStream specified");
        AssertUtils.notNull(out, "No OutputStream specified");

        int byteCount = 0;
        byte[] buffer = new byte[BUFFER_SIZE];
        int bytesRead;
        while ((bytesRead = in.read(buffer)) != -1) {
            out.write(buffer, 0, bytesRead);
            byteCount += bytesRead;
        }
        out.flush();
        return byteCount;
    }

    private static InputStream defaultNullInputStream() {
        return new InputStream() {
            private volatile boolean closed;

            private void ensureOpen() throws IOException {
                if (closed) {
                    throw new IOException("Stream closed");
                }
            }

            @Override
            public int available() throws IOException {
                ensureOpen();
                return 0;
            }

            @Override
            public int read() throws IOException {
                ensureOpen();
                return -1;
            }

            @Override
            public int read(byte[] b, int off, int len) throws IOException {
                checkFromIndexSize(off, len, b.length);
                if (len == 0) {
                    return 0;
                }
                ensureOpen();
                return -1;
            }

            @Override
            public long skip(long n) throws IOException {
                ensureOpen();
                return 0L;
            }

            @Override
            public void close() {
                closed = true;
            }
        };
    }

    public static int checkFromIndexSize(int fromIndex, int size, int length) {
        return Preconditions.checkFromIndexSize(fromIndex, size, length, null);
    }
}
